<?php

namespace App\Modules\LuckDraw\Strategy;

use \App\Modules\LuckDraw\Award\AwardFactory;
use App\Modules\LuckDraw\Award\AwardInterface;
use App\Modules\LuckDraw\LuckDraw\LuckDraw;
use App\Modules\LuckDraw\LuckDraw\LuckDrawFactory;
use App\Modules\LuckDraw\Models\CompensateLogModel;
use Illuminate\Support\Facades\Redis;

class PoolWithCompensateStrategy implements StrategyInterface {
    private $_name = "pool_with_compensate";
    private $_luckId;
    private $_userId;
    private $_ext;
    private $_pool;
    private $_awardRedisKey; //存放奖池的 //不过期
    private $_periodDrawNumRedisKey; //存放一轮抽奖的次数 //不过期
    private $_totalDrawNumRedisKey; //存放抽奖总次数的 //不过期
    private $_weekProfitRedisKey; //存放收益表的 //过期时间1周
    private $_compensateRedisKey; //存放补偿的 //过期时间1天

    private $_compensateAwardLogArr; //从补偿列表里拿出来的奖品放这里，用于保存数据库失败了以后回滚

    public function __construct(int $luckId, int $userId, ?object $ext, object $pool){
        $this->_luckId = $luckId;
        $this->_userId = $userId;
        $this->_ext = $ext;
        $this->_pool = $pool;
        $this->_awardRedisKey = sprintf('{LUCKDRAW:STRATEGY:POOL_WITH_COMPENSATE:DRAW:%s:POOL:%s}', $luckId, $pool->id);
        $this->_periodDrawNumRedisKey = sprintf('LUCKDRAW:STRATEGY:POOL_WITH_COMPENSATE:PERIOD:DRAW:%s:POOL:%s', $luckId,$pool->id);
        $this->_totalDrawNumRedisKey = sprintf('LUCKDRAW:STRATEGY:POOL_WITH_COMPENSATE:DRAW_NUM:DRAW:%s:POOL:%s', $luckId,$pool->id);
        $this->_weekProfitRedisKey = sprintf('LUCKDRAW:STRATEGY:POOL_WITH_COMPENSATE:PROFIT:DRAW:%s:POOL:%s:%s', $luckId,$pool->id,date('Y-m-d', strtotime('this week')));
        $this->_compensateRedisKey = sprintf('LUCKDRAW:STRATEGY:POOL_WITH_COMPENSATE:COMPENSATE:DRAW:%s:POOL:%s', $luckId,$pool->id);
        $this->_compensateAwardLogArr = [];
    }

    public function pool():?object{
        return $this->_pool;
    }

    public function poolId():?int{
        return $this->pool()->id;
    }

    public function name():?string{
        return $this->_name;
    }

    public function run(int $num) : ?array {
        if ($num <= 0) {
            return [];
        }
        //随机取出$num个奖品 //返回用的
        $awardArr = [];

        //根据总的抽奖次数 判断是否该补偿用户奖品了
        $totalHasDrawNum = $this->_addDrawNum($num);

        //看是否有当前用户的补偿奖品 //用户自然周内赔的最多的就补偿
        list($compensateNum, $awardArr) = $this->_getUserCompensates($num);

        for ($i = 0; $i < $num - $compensateNum; $i++) {
            //从集合里随机拿一个奖品
            $award = $this->_pop();
            //为空则奖品池没有初始化
            if ($award == null) {
                //开始初始化
                $ok = $this->_initAwardPool($num);
                $totalHasDrawNum = $this->_addDrawNum($num); //初始化以后总的抽奖次数被置为0，需要重新获取抽奖次数
                //如果初始化失败返回null
                if (!$ok) {
                    return null;
                }
                $award = $this->_pop();
            }

            if ($award == null) {
                return null;
            }
            $k = $award[0] . '-' . $award[1] . '-' . $award[2];
            if (isset($awardArr[$k])) {
                $awardArr[$k]->num++; //数量加
                continue;
            }
            //转化成对象
            $award =  AwardFactory::get(
                $this->_luckId,
                $this->_userId,
                $award[0],
                $award[1],
                $award[2],
                $award[3]
            );
            $awardArr[$k] = $award;
        }

        //设置用户收益 //完善的时候在判断是否设置成功
        $ok = $this->_setUserProfit($num, $awardArr);

        //设置用户补偿信息
//        $periodNum = $this->_getPeriod();
        $periodNum = $this->_getTotalAwardNum();
        list($totalCompensateNum, $tmpCompensateAwards) = $this->_getCompensateAwards();
        if (($totalHasDrawNum -$num) % $periodNum < ($periodNum - $totalCompensateNum) &&  (($totalHasDrawNum -$num) % $periodNum + $num) >= ($periodNum - $totalCompensateNum)) {
            $ok = $this->_setUserCompensate();
        }
        $retAwards = array_values($awardArr);
        
        //成功返回奖品列表
        return $retAwards;
    }

    private function _getUserCompensates($num): array {
        $poolData = $this->_getPoolData();
        $curPoolAwards = [];
        foreach ($poolData as $poolAward) {
            array_push($curPoolAwards, $poolAward->type . '-' . $poolAward->id . '-' . $poolAward->time);
        }
        $compensateNum = 0;
        $awardArr = [];
        while ($compensateNum < $num) {
            $award = $this->_popUserCompensate();
            if (empty($award)) {
                break;
            }
            $key = $award->type . '-' . $award->awardId . '-' . $award->timeLimit;
            if (!in_array($key, $curPoolAwards)) { //如果不是当前奖池的奖品，跳过
                continue;
            }
            if (isset($awardArr[$key])) {
                $awardArr[$key]->num ++;
            } else {
                $award->num = 1;
                $awardArr[$key] = $award;
            }
            $compensateNum ++;
        }
        return [$compensateNum, $awardArr];
    }

    private function  _pop() : ?array {
        $luaStr = "local funname = 'spop'
                KEYS[5] = KEYS[1] .. ':0'
                KEYS[6] = KEYS[1] .. ':1'
                KEYS[7] = KEYS[1] .. ':POOLNAME'
                local tmpA = redis.call('get', KEYS[7]);
                if tmpA then
                    KEYS[5] = tmpA
                end
                if  KEYS[5] == KEYS[1] .. ':1' then
                    KEYS[6] = KEYS[1] .. ':0'
                end
                local val = redis.call('spop',KEYS[5])
                if val then
                    redis.call('sadd', KEYS[6], val)
                    return val
                else
                    val = redis.call('spop',KEYS[6])
                    if val then
                        redis.call('sadd', KEYS[5], val)
                        redis.call('set', KEYS[7], KEYS[6])
                        return val
                    end
                end
                return nil
                ";
        $val = Redis::EVAL($luaStr, 1, $this->_awardRedisKey);
        if ($val == null) {
            return null;
        }
        list($index, $type, $awardId, $timeLimit, $price) = explode('-', $val);

        return [$type, $awardId, $timeLimit, $price];
    }

    //初始化奖品数据
    private function _initAwardPool (int $num) : bool {
        //已经存在了就不需要再初始化了
        if (Redis::EXISTS($this->_awardRedisKey . ":0") || Redis::EXISTS($this->_awardRedisKey . ":1")) {
            return false;
        }

        //拿出奖池数据
        $poolData =  $this->_getPoolData();
        $curPoolAwards = [];
        foreach ($poolData as $poolAward) {
            array_push($curPoolAwards, $poolAward->type . '-' . $poolAward->id . '-' . $poolAward->time);
        }

        $lock = redis_lock($this->_awardRedisKey . ":0");
        if (!$lock) {
            redis_unlock($this->_awardRedisKey . ":0");
            return false;
        }


        list($tmpCompensateAwardNum, $compensateArr) = $this->_getCompensateAwards();

        $res = Redis::Pipeline(function($pipe) use($poolData, $compensateArr) {
            $pipe->set($this->_awardRedisKey . ":POOLNAME", $this->_awardRedisKey . ":0");
            $pipe->set($this->_totalDrawNumRedisKey, 0);
            $index = 1;
            foreach ($poolData as $award) {
                $key = $award->type . '-' . $award->id . '-' . $award->time;
                if (isset($compensateArr[$key])) {
                    $award->num = $award->num - $compensateArr[$key]->num;
                }
                for ($i = 0; $i < $award->num; $i++) {
                    $pipe->sadd($this->_awardRedisKey . ":0", $index++ . '-' . $award->type . '-' . $award->id . '-' . $award->time . '-' . $award->price);
                }
            }
        });

        redis_unlock($this->_awardRedisKey . ":0");
        return true;
    }

    private function  _setUserProfit(int $num, array $awards): bool {
        $price = LuckDrawFactory::get($this->_luckId)->getPrice();
        $totalPay = $price * $num;
        $totalIncome = 0;
        foreach ($awards as $award) {
            $totalIncome += $award->price * $award->num;
            write_log("LuckDraw", "luck_draw_awards", [$price, $award->price ,  $award->num]);
        }
        write_log("LuckDraw", "luck_draw_profit", [$price, $num, $totalIncome, $totalPay]);
        //如果key 不存在 设置过期时间
        if (!Redis::EXISTS($this->_weekProfitRedisKey)) {
            Redis::ZINCRBY($this->_weekProfitRedisKey,  $totalIncome-$totalPay, $this->_userId);
            //设置7天零一个小时的过期时间
            Redis::EXPIRE($this->_weekProfitRedisKey, 7 * 3600 * 24 + 3600);
        } else {
            Redis::ZINCRBY($this->_weekProfitRedisKey, $totalIncome-$totalPay, $this->_userId);
        }
        return true;
    }


    //设置抽奖一轮的次数
    private function _setPeriod($num) : bool {
        $result = Redis::SET($this->_periodDrawNumRedisKey,$num);
        return $result->getPayload() == "OK";
    }

    //获取抽奖一轮的次数
    private function _getPeriod() : int {
        return Redis::GET($this->_periodDrawNumRedisKey)??0;
    }

    //获取补偿的奖品
//    private function _getCompensateAwards() : ?array {
//        return $this->_ext->awards;
//    }

    private function _getCompensateAwards() : array {
        $curPoolAwards = [];
        $poolData = $this->_getPoolData();
        foreach ($poolData as $poolAward) {
            $curPoolAwards[$poolAward->type . '-' . $poolAward->id . '-' . $poolAward->time] = $poolAward->num;
        }
        $compensates =  $this->_ext->awards;
        $compensateArr = [];
        $totalCompensateNum = 0;
        foreach ($compensates as $compensate) {
            $key = $compensate->type . '-' . $compensate->id . '-' . $compensate->time;
            //添加奖品数量为0的情况
            if (!in_array($key, array_keys($curPoolAwards)) && $curPoolAwards[$key] <= 0) {
                continue;
            }
            $compensateArr[$key] = $compensate;
            //添加 补偿数量与奖池数量大小判断
            $totalCompensateNum += ($compensate->num > $curPoolAwards[$key]?$curPoolAwards[$key]:$compensate->num);
        }
        return [$totalCompensateNum, $compensateArr];
    }

    //获取补偿的收益阈值
    private function _getThreshold() : ?int {
        return $this->_ext->threshold;
    }

    //增加抽奖次数
    private function _addDrawNum(int $num):int{
        return Redis::INCRBY($this->_totalDrawNumRedisKey,  $num);
    }

    private function _initDrawNum(){
        return Redis::SET($this->_totalDrawNumRedisKey,  0);
    }

    //获取抽奖次数
    private function _getDrawNum(): int {
        return Redis::GET($this->_totalDrawNumRedisKey);
    }

    //设置用户补偿
    private function _setUserCompensate(): bool {
        $result = Redis::ZRANGEBYSCORE($this->_weekProfitRedisKey, '-inf', '+inf', array('withscores'=>true, 'limit'=>array(0, 1)));
        $userId = key($result);
        $profit = current($result);
        if (!$userId || !$profit) {
            return true;
        }
        if ($this->_getThreshold() + $profit > 0) {
            return true;
        }
        list($tmpCompensateAwardNum, $compensateAwards) = $this->_getCompensateAwards();
        $lock = redis_lock($this->_compensateRedisKey . ":" . $userId);
        if (!$lock) {
            return false;
        }
        $res = Redis::Pipeline(function($pipe) use($compensateAwards, $userId) {
            $index = 1;
            foreach ($compensateAwards as $award) {
                $key = $award->type . '-' . $award->id . '-' . $award->time . '-' . $award->price;
                for ($i = 0; $i< $award->num; $i++) {
                    $pipe->sadd($this->_compensateRedisKey . ':' . $userId, $index++ . '-' . $key);
                }
            }
            //补偿奖品过期时间增加到7天
            $pipe->expire($this->_compensateRedisKey . ":" . $userId, 3600 * 24 * 7);
        });
        redis_unlock($this->_compensateRedisKey . ":" . $userId);
        return true;
    }

    //获取一个用户补偿
    private function _popUserCompensate(): ?AwardInterface{
        $a = Redis::SPOP($this->_compensateRedisKey . ':' . $this->_userId);
        if (empty($a)) {
            return null;
        }
        list($index, $type, $awardId, $timeLimit, $price) = explode('-', $a);
        $award = AwardFactory::get($this->_luckId, $this->_userId, $type,$awardId,$timeLimit,$price);
        $ok = $this->_logUserCompensate($award); //后边完善判断
        return $award;
    }

    private function _logUserCompensate(AwardInterface $award): bool {
        $compensateLogModel = CompensateLogModel::log($this->_userId, $this->_luckId, $this->poolId(),
            $award->awardId, $award->type, $award->timeLimit, $award->price);
        if ($compensateLogModel != null) {
            $this->_compensateAwardLogArr[] = $compensateLogModel;
        }
        return $compensateLogModel != null;
    }

    private function _getTotalAwardNum(){
        $poolData = $this->_getPoolData();
        $totalAwardNum = 0;
        foreach ($poolData as $award) {
            $totalAwardNum += $award->num;
        }
        return $totalAwardNum;
    }

    //根据奖池id，获取奖池数据
    function _getPoolData(): ?array{
        return json_decode($this->pool()->pool);
    }

    //获取奖池数据
    function getPoolData(): ?array{
        return json_decode($this->pool()->pool);
    }

    //抽奖失败的回滚
    public function rollBack() {
        // TODO: Implement rollBack() method.
        foreach ($this->_compensateAwardLogArr as $compensateAwardLog) {
            $compensateAwardLog->status = 0;
            $compensateAwardLog->save();
        }
    }

    public function delPoolRedis():?bool {
        write_log('ActivityPool','清除redis',$this->_awardRedisKey);
        write_log('ActivityPool','清除redis',$this->_periodDrawNumRedisKey);
        write_log('ActivityPool','清除redis',$this->_totalDrawNumRedisKey);
        Redis::del($this->_awardRedisKey.':0');
        Redis::del($this->_awardRedisKey.':1');
        Redis::del($this->_awardRedisKey.':POOLNAME');
        Redis::del($this->_periodDrawNumRedisKey);
        Redis::del($this->_totalDrawNumRedisKey);
        return true;
    }

    public function upAwardStrategy(array $poolDatas):?array{
        //校验补偿
        $strategy =$this->_ext;
        $strategy = json_decode(json_encode($strategy,true),JSON_UNESCAPED_UNICODE);
        write_log('ActivityPool','upAwardStrategy开始',$strategy);
        $award = $strategy['awards']??[];
        if (!$award){
            return [];
        }
        $awardByKey = [];
        foreach ($award as $awardData){
            $key = $awardData['id'].'|'.$awardData['type'];
            $awardByKey[$key] = $awardData;
        }
        //补偿变更状态
        $status = 0;
        foreach ($poolDatas as $poolData){
            if (!isset($poolData['old_data'])){
                continue;
            }
            $key = $poolData['old_data']['id'].'|'.$poolData['old_data']['type'];
            if (!in_array($key,array_keys($awardByKey)) ){
                continue;
            }
            $status = 1;
            $newKey = $poolData['id'].'|'.$poolData['type'];
            if ($key == $newKey){
                continue;
            }

            $awardByKey[$newKey] = [
                'id' => $poolData['id'],
                'num' => $awardByKey[$key]['num'],
                'time' => $poolData['time'],
                'type' => $poolData['type'],
                'price' => $poolData['price']
            ];
            unset($awardByKey[$key]);
        }
        $awardByKey = array_values($awardByKey);
        $strategy['awards'] = $awardByKey;
        write_log('ActivityPool','upAwardStrategy结束',$strategy);
        return [
            'status' =>  $status,
            'ext' => $strategy
        ];
    }

    public static function clearCacheByPoolId(int $luckId, array $userList, int $poolId): bool {
        //清除所有缓存
        $res = Redis::Pipeline(function($pipe) use($luckId, $userList, $poolId) {
            $awardRedisKey = sprintf('{LUCKDRAW:STRATEGY:POOL_WITH_COMPENSATE:DRAW:%s:POOL:%s}', $luckId, $poolId);
            $totalDrawNumRedisKey = sprintf('LUCKDRAW:STRATEGY:POOL_WITH_COMPENSATE:DRAW_NUM:DRAW:%s:POOL:%s', $luckId, $poolId);
            $pipe->del($awardRedisKey . ":POOLNAME");
            $pipe->del($totalDrawNumRedisKey);
            $pipe->del($awardRedisKey . ':0');
            $pipe->del($awardRedisKey . ':1');
        });
        return true;
    }
}
